package mongodb.general.operator.mongoHelper.db.impl;

import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import mongodb.general.operator.mongoHelper.common.Page;
import mongodb.general.operator.mongoHelper.common.annotations.CreateTime;
import mongodb.general.operator.mongoHelper.common.annotations.UpdateTime;
import mongodb.general.operator.mongoHelper.common.builder.SortBuilder;
import mongodb.general.operator.mongoHelper.db.MongoHelper;
import mongodb.general.operator.mongoHelper.init.InitValue;
import mongodb.general.operator.mongoHelper.log.MongoLogHelper;
import mongodb.general.operator.mongoHelper.log.enums.LogTypeEnum;
import mongodb.general.operator.mongoHelper.reflection.ReflectionUtil;
import mongodb.general.operator.mongoHelper.reflection.SerializableFunction;
import mongodb.general.operator.mongoHelper.wrapper.CriteriaAndWrapper;
import mongodb.general.operator.mongoHelper.wrapper.CriteriaWrapper;
import mongodb.general.operator.config.Constant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;

import java.lang.reflect.Field;
import java.util.*;

@Service
public class MongoHelperImpl implements MongoHelper {

    @Autowired
    private MongoLogHelper logHelper;
    @Autowired
    private MongoTemplate mongoTemplate;

    @Override
    public <T> T findById(String id, Class<T> clazz) {
        if (StrUtil.isEmpty(id)) {
            return null;
        }
        Long systemTime = System.currentTimeMillis();

        T t = (T) mongoTemplate.findById(id, clazz);

        CriteriaAndWrapper criteriaAndWrapper = new CriteriaAndWrapper().eq(Constant::getId, id);
        logHelper.log(clazz,new Query(criteriaAndWrapper.build()),
                systemTime,LogTypeEnum.Query);
        return t;
    }

    @Override
    public <T> List<T> findListByIds(String[] ids, Class<T> clazz) {
        List<String> strings = Arrays.asList(ids);
        CriteriaAndWrapper criteriaAndWrapper =
                new CriteriaAndWrapper().in(Constant::getId, strings);
        SortBuilder sortBuilder = new SortBuilder().add(Constant::getId, Sort.Direction.DESC);
        return findListByQuery(criteriaAndWrapper, sortBuilder,clazz);
    }

    /**
     * 查找数量
     *
     * @param criteriaWrapper 查询
     * @param clazz    类
     * @return Long 数量
     */
    public Long findCountByQuery(CriteriaWrapper criteriaWrapper, Class<?> clazz) {
        Long systemTime = System.currentTimeMillis();
        Long count = null;

        Query query = new Query(criteriaWrapper.build());
        if (query.getQueryObject().isEmpty()) {
            count = mongoTemplate.getCollection(mongoTemplate.getCollectionName(clazz)).estimatedDocumentCount();
        } else {
            count = mongoTemplate.count(query, clazz);
        }
        logHelper.log(clazz, query, systemTime,LogTypeEnum.Count);
        return count;
    }

    @Override
    public Long findAllCount(Class<?> clazz) {
        return findCountByQuery(new CriteriaAndWrapper(), clazz);
    }
    //SortBuilder sortBuilder = new SortBuilder(Constant::getId, Direction.DESC);
    //new CriteriaAndWrapper()
    @Override
    public <T> T findOneByQuery(CriteriaWrapper criteriaWrapper, SortBuilder sortBuilder, Class<T> clazz) {
        Query query = new Query(criteriaWrapper.build());
        query.limit(1);
        query.with(sortBuilder.toSort());
        Long systemTime = System.currentTimeMillis();
        T t = (T) mongoTemplate.findOne(query, clazz);
        logHelper.log(clazz, query, systemTime,LogTypeEnum.Query);
        return t;
    }


    //SortBuilder sortBuilder = new SortBuilder(Constant::getId, Direction.DESC);
    //new CriteriaAndWrapper()
    @Override
    public <T> List<T> findListByQuery(CriteriaWrapper criteriaWrapper, SortBuilder sortBuilder, Class<T> clazz) {
        Query query = new Query(criteriaWrapper.build());
        query.with(sortBuilder.toSort());
        Long systemTime = System.currentTimeMillis();
        List<T> list = mongoTemplate.find(query, clazz);
        logHelper.log(clazz, query, systemTime,LogTypeEnum.Query);
        return list;
    }

    @Override
    public List<Object> findListByQuery(CriteriaWrapper criteriaWrapper, SortBuilder sortBuilder, String tableName) {
        Query query = new Query(criteriaWrapper.build());
        query.with(sortBuilder.toSort());
//        Long systemTime = System.currentTimeMillis();
        List<Object> list = mongoTemplate.find(query, Object.class,tableName);
//        logHelper.log(clazz, query, systemTime,LogTypeEnum.Query);
        return list;
    }

    //new CriteriaAndWrapper()
    //obj.class
    //Constant::getId
    //String.class
    @Override
    public <T, R, E> List<T> findPropertiesByQuery(CriteriaWrapper criteriaWrapper, Class<?> documentClass, SerializableFunction<E, R> property, Class<T> propertyClass) {
        Query query = new Query(criteriaWrapper.build());
        query.fields().include(ReflectionUtil.getFieldName(property));

        Long systemTime = System.currentTimeMillis();
        List<?> list = mongoTemplate.find(query, documentClass);
        logHelper.log(documentClass, query, systemTime,LogTypeEnum.Query);
        List<T> propertyList = extractProperty(list, ReflectionUtil.getFieldName(property), propertyClass);
        return propertyList;
    }


    /**
     * 获取list中对象某个属性,组成新的list
     * @param list     列表
     * @param clazz    类
     * @param property 属性
     * @return List<T> 列表
     */
    @SuppressWarnings("unchecked")
    private <T> List<T> extractProperty(List<?> list, String property, Class<T> clazz) {
        Set<T> rs = new HashSet<T>();
        for (Object object : list) {
            Object value = ReflectUtil.getFieldValue(object, property);
            if (value != null && value.getClass().equals(clazz)) {
                rs.add((T) value);
            }
        }
        return new ArrayList<T>(rs);
    }

    @Override
    public String insertOrUpdate(Object object) {
        Long time = System.currentTimeMillis();
        String id = (String) ReflectUtil.getFieldValue(object, Constant.ID);
        Object objectOrg = StrUtil.isNotEmpty(id) ?
                findById(id, object.getClass()) : null;

        if (objectOrg == null) {
            // 插入
            // 设置插入时间
            setCreateTime(object, time);
            // 设置更新时间
            setUpdateTime(object, time);
            // 设置默认值
            setDefaultVaule(object);
            // 去除id值
            ReflectUtil.setFieldValue(object, Constant.ID, null);
            mongoTemplate.save(object);
            id = (String) ReflectUtil.getFieldValue(object, Constant.ID);
            logHelper.logSave(object, time, true);
        } else {
            // 更新
            Field[] fields = ReflectUtil.getFields(object.getClass());
            // 拷贝属性
            for (Field field : fields) {
                if (!field.getName().equals(Constant.ID) && ReflectUtil.getFieldValue(object, field) != null) {
                    ReflectUtil.setFieldValue(objectOrg, field, ReflectUtil.getFieldValue(object, field));
                }
            }
            // 设置更新时间
            setUpdateTime(objectOrg, time);
            mongoTemplate.save(objectOrg);
            logHelper.logSave(objectOrg, time, false);
        }
        return id;
    }

    @Override
    public <T> void insertAll(List<T> list) {
        Long time = System.currentTimeMillis();
        for (Object object : list) {
            // 去除id以便插入
            ReflectUtil.setFieldValue(object, Constant.ID, null);
            // 设置插入时间
            setCreateTime(object, time);
            // 设置更新时间
            setUpdateTime(object, time);
            // 设置默认值
            setDefaultVaule(object);
        }
        mongoTemplate.insertAll(list);
        logHelper.logSave(list, time);
    }

    @Override
    public void updateById(Object object) {
        if (StrUtil.isEmpty((String) ReflectUtil.getFieldValue(object, Constant.ID))) {
            return;
        }
        if (findById((String) ReflectUtil.getFieldValue(object, Constant.ID), object.getClass()) == null) {
            return;
        }
        insertOrUpdate(object);
    }

    @Override
    public void updateAllColumnById(Object object) {
        if (StrUtil.isEmpty((String) ReflectUtil.getFieldValue(object, Constant.ID))) {
            return;
        }
        if (findById((String) ReflectUtil.getFieldValue(object, Constant.ID), object.getClass()) == null) {
            return;
        }
        Long time = System.currentTimeMillis();
        setUpdateTime(object, time);
        mongoTemplate.save(object);
        logHelper.logSave(object, time, false);
    }

    /**
     * 根据条件删除
     * @param criteriaWrapper 查询
     * @param clazz    类
     */
    public void deleteByQuery(CriteriaWrapper criteriaWrapper, Class<?> clazz) {
        Long time = System.currentTimeMillis();
        Query query = new Query(criteriaWrapper.build());
        mongoTemplate.remove(query, clazz);
        logHelper.log(clazz, query, time,LogTypeEnum.Delete);
    }
    @Override
    public void deleteById(String id, Class<?> clazz) {
        if (StrUtil.isEmpty(id)) {
            return;
        }
        deleteByQuery(new CriteriaAndWrapper().eq(Constant::getId, id), clazz);
    }
    @Override
    public void deleteByIds(List<String> ids, Class<?> clazz) {
        if (ids == null || ids.size() == 0) {
            return;
        }
        deleteByQuery(new CriteriaAndWrapper().in(Constant::getId, ids), clazz);
    }
    /**
     * 按查询条件获取Page
     * @param criteriaWrapper 查询
     * @param sortBuilder     排序
     * @param clazz    类
     * @return Page 分页
     */
    @Override
    public <T> Page<T> findPage(CriteriaWrapper criteriaWrapper, SortBuilder sortBuilder, Page<?> page, Class<T> clazz) {

        Page<T> pageResp = new Page<T>();
        pageResp.setCurr(page.getCurr());
        pageResp.setLimit(page.getLimit());
        // 查询出总条数
        if (page.getQueryCount()) {
            Long count = findCountByQuery(criteriaWrapper, clazz);
            pageResp.setCount(count);
        }
        // 查询List
        Query query = new Query(criteriaWrapper.build());
        query.with(sortBuilder.toSort());
        query.skip((page.getCurr() - 1) * page.getLimit());// 从那条记录开始
        query.limit(page.getLimit());// 取多少条记录

        Long systemTime = System.currentTimeMillis();
        List<T> list = mongoTemplate.find(query, clazz);
        logHelper.log(clazz, query, systemTime,LogTypeEnum.Query);
        pageResp.setList(list);
        return pageResp;
    }
    /**
     * 按查询条件获取Page
     *
     * @param criteriaWrapper 查询
     * @param page     分页
     * @param clazz    类
     * @return Page 分页
     */
    public <T> Page<T> findPage(CriteriaWrapper criteriaWrapper, Page<?> page, Class<T> clazz) {
        SortBuilder sortBuilder = new SortBuilder(Constant::getId, Sort.Direction.DESC);
        return findPage(criteriaWrapper, sortBuilder, page, clazz);
    }
    @Override
    public <T> Page<T> findPage(Page<?> page, Class<T> clazz) {
        return findPage(new CriteriaAndWrapper(), page, clazz);
    }

    @Override
    public <T> Page<T> findPage(SortBuilder sortBuilder, Page<?> page, Class<T> clazz) {
        return findPage(new CriteriaAndWrapper(), sortBuilder, page, clazz);
    }

    /**
     * 设置创建时间
     *
     * @param object 对象
     */
    private void setCreateTime(Object object, Long time) {
        Field[] fields = ReflectUtil.getFields(object.getClass());
        for (Field field : fields) {
            // 获取注解
            if (field.isAnnotationPresent(CreateTime.class) && field.getType().equals(Long.class)) {
                ReflectUtil.setFieldValue(object, field, time);
            }
        }
    }
    /**
     * 设置更新时间
     *
     * @param object 对象
     */
    private void setUpdateTime(Object object, Long time) {
        Field[] fields = ReflectUtil.getFields(object.getClass());
        for (Field field : fields) {
            // 获取注解
            if (field.isAnnotationPresent(UpdateTime.class) && field.getType().equals(Long.class)) {
                ReflectUtil.setFieldValue(object, field, time);
            }
        }
    }
    /**
     * 设置默认值
     *
     * @param object 对象
     */
    private void setDefaultVaule(Object object) {
        Field[] fields = ReflectUtil.getFields(object.getClass());
        for (Field field : fields) {
            // 获取注解
            if (field.isAnnotationPresent(InitValue.class)) {
                InitValue defaultValue = field.getAnnotation(InitValue.class);

                String value = defaultValue.value();

                if (ReflectUtil.getFieldValue(object, field) == null) {
                    // 获取字段类型
                    Class<?> type = field.getType();
                    if (type.equals(String.class)) {
                        ReflectUtil.setFieldValue(object, field, value);
                    }
                    if (type.equals(Short.class)) {
                        ReflectUtil.setFieldValue(object, field, Short.parseShort(value));
                    }
                    if (type.equals(Integer.class)) {
                        ReflectUtil.setFieldValue(object, field, Integer.parseInt(value));
                    }
                    if (type.equals(Long.class)) {
                        ReflectUtil.setFieldValue(object, field, Long.parseLong(value));
                    }
                    if (type.equals(Float.class)) {
                        ReflectUtil.setFieldValue(object, field, Float.parseFloat(value));
                    }
                    if (type.equals(Double.class)) {
                        ReflectUtil.setFieldValue(object, field, Double.parseDouble(value));
                    }
                    if (type.equals(Boolean.class)) {
                        ReflectUtil.setFieldValue(object, field, Boolean.parseBoolean(value));
                    }
                }
            }
        }
    }

//    /**
//     * 更新查到的第一项
//     *
//     * @param criteria 查询
//     * @param update   更新
//     * @param clazz    类
//     */
//    public void updateFirst(CriteriaWrapper criteriaWrapper, UpdateBuilder updateBuilder, Class<?> clazz) {
//        Long time = System.currentTimeMillis();
//        Query query = new Query(criteriaWrapper.build());
//
//        mongoTemplate.updateFirst(query, updateBuilder.toUpdate(), clazz);
//        logUpdate(clazz, query, updateBuilder, false, time);
//    }
//
//    /**
//     * 更新查到的全部项
//     *
//     * @param criteria 查询
//     * @param update   更新
//     * @param clazz    类
//     */
//    public void updateMulti(CriteriaWrapper criteriaWrapper, UpdateBuilder updateBuilder, Class<?> clazz) {
//
//        Long time = System.currentTimeMillis();
//        Query query = new Query(criteriaWrapper.build());
//        mongoTemplate.updateMulti(new Query(criteriaWrapper.build()), updateBuilder.toUpdate(), clazz);
//        logUpdate(clazz, query, updateBuilder, true, time);
//    }
//
//    /**
//     * 累加某一个字段的数量,原子操作
//     *
//     * @param object
//     */
//    public <R, E> void addCountById(String id, SerializableFunction<E, R> property, Number count, Class<?> clazz) {
//        UpdateBuilder updateBuilder = new UpdateBuilder().inc(property, count);
//
//        updateFirst(new CriteriaAndWrapper().eq(Constant::getId, id), updateBuilder, clazz);
//    }

}
